package com.cloudfun.campusshare.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.cloudfun.campusshare.common.constant.*;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.vo.*;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.entity.AttachmentEntity;
import com.cloudfun.campusshare.entity.LoanEntity;
import com.cloudfun.campusshare.entity.ProjectEntity;
import com.cloudfun.campusshare.entity.SchoolEntity;
import com.cloudfun.campusshare.repo.*;
import com.cloudfun.campusshare.service.AttachmentService;
import com.cloudfun.campusshare.service.DeviceService;
import com.cloudfun.campusshare.service.ProjectService;
import com.cloudfun.campusshare.util.AmountUtil;
import com.cloudfun.campusshare.util.QueryUtil;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author : ashsr.zhang
 * @date : 2020/3/3 19:11
 * @Version : v1.0
 * @description
 **/
@Service
public class ProjectServiceImpl implements ProjectService {
    @Resource
    private ProjectEntityRepo projectEntityRepo;
    @Resource
    private AttachmentEntityRepo attachmentEntityRepo;
    @Autowired
    private AttachmentService attachmentService;
    @Autowired
    private DeviceEntityRepo deviceEntityRepo;
    @Autowired
    private EntityManager entityManager;
    @Resource
    private LoanEntityRepo loanEntityRepo;
    @Autowired
    private DeviceService deviceService;
    @Resource
    private SchoolEntityRepository schoolEntityRepository;

    @Override
    public void createProject(ProjectAddVO projectAddVO) {
        String bidId = projectAddVO.getBidId();
        Optional<AttachmentEntity> attach = attachmentEntityRepo.findById(bidId);
        if (!attach.isPresent()) {
            throw new RuntimeException("该id:" + bidId + "对应文件不存在");
        }
        Optional<SchoolEntity> school = schoolEntityRepository.findById(projectAddVO.getSchoolId());
        if (!school.isPresent()) {
            throw new BusinessException(Code.ERROR, "该学校id所对应的学校不存在");
        }
        SchoolEntity schoolEntity = school.get();
        long now = System.currentTimeMillis();
        long winTime = projectAddVO.getWinningBidTime().getTime();
        long opsTime = projectAddVO.getOperationStartTime().getTime();
        long pdTime = projectAddVO.getProjectDueTime().getTime();
        if (winTime > opsTime || opsTime > pdTime || winTime > pdTime) {
            throw new RuntimeException("请输入正确的时间");
        }

        if (now < winTime) {
            throw new RuntimeException("项目中标时间不得晚于当前时间！");
        }
        if (opsTime < now) {
            throw new RuntimeException("项目运营时间不得早于当前时间！");
        }

        if (projectAddVO.getId() != null) {
            ProjectEntity oldProject = projectEntityRepo.findById(projectAddVO.getId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            String loanId = oldProject.getLoanId();
            if (StringUtils.isBlank(loanId) || loanEntityRepo.findById(loanId).orElseThrow(() -> new BusinessException(Code.NODATA)).getStatus().equals(LoanStatus.CREATING)) {
                oldProject.setName(projectAddVO.getName());
                oldProject.setBelongSchool(schoolEntity.getName());
                oldProject.setDeviceCount(projectAddVO.getDeviceCount());
                oldProject.setType(ProjectType.getTypeByCode(projectAddVO.getType()));
                oldProject.setOperationStartTime(String.valueOf(projectAddVO.getOperationStartTime().getTime()));
                oldProject.setWinningBidTime(String.valueOf(projectAddVO.getWinningBidTime().getTime()));
                oldProject.setProjectDueTime(String.valueOf(projectAddVO.getProjectDueTime().getTime()));
                oldProject.setProjectSummary(projectAddVO.getProjectSummary());
                oldProject.setSchoolId(projectAddVO.getSchoolId());
                List<AttachmentEntity> attachment = attachmentEntityRepo.findAllBySubjectIdAndType(oldProject.getId(), AttachType.BID_DOCUMENT);
                if (CollectionUtils.isEmpty(attachment)) {
                    throw new RuntimeException("该项目没有关联的标书文件！");
                }
                AttachmentEntity attachmentEntity = attachment.get(0);
                if (!bidId.equals(attachmentEntity.getId())) {
                    attachmentEntity.setSubjectId(null);
                    attachmentEntityRepo.save(attachmentEntity);
                    attachmentService.relateAttachment(oldProject.getId(), bidId, AttachType.BID_DOCUMENT);
                }

                projectEntityRepo.save(oldProject);
            } else {
                throw new RuntimeException("该项目已经有贷款申请,无法更新");
            }
        } else {

            ProjectEntity projectEntity = ProjectEntity.builder()
                    .name(projectAddVO.getName())
                    .type(ProjectType.getTypeByCode(projectAddVO.getType()))
                    .belongSchool(schoolEntity.getName())
                    .schoolId(projectAddVO.getSchoolId())
                    .winningBidTime(String.valueOf(projectAddVO.getWinningBidTime().getTime()))
                    .operationStartTime(String.valueOf(projectAddVO.getOperationStartTime().getTime()))
                    .projectDueTime(String.valueOf(projectAddVO.getProjectDueTime().getTime()))
                    .deviceCount(projectAddVO.getDeviceCount())
                    .projectSummary(projectAddVO.getProjectSummary())
                    .userId(AuthenticationHolder.getUserId())
                    .build();
            projectEntity.setStatus(ProjectStatus.PREPARE);


            ProjectEntity save = projectEntityRepo.save(projectEntity);
            AttachmentEntity attachmentEntity = attach.get();
            attachmentService.relateAttachment(save.getId(), attachmentEntity.getId(), AttachType.BID_DOCUMENT);
        }
    }

    @Override
    public PageResult<ProjectListVO> queryProjectList(Pageable pageable, String keyword, String userId) {
        Specification<ProjectEntity> spec = this.getSpec(keyword, userId);
        Page<ProjectEntity> page = projectEntityRepo.findAll(spec, pageable);
        List<ProjectEntity> projectEntities = page.getContent();
        Map<String, String> projectIdDeviceOrderTotalAmountMap = new HashMap<>();
        if (AuthenticationHolder.isAdmin()) {
            Set<String> projectIds = projectEntities.stream().map(ProjectEntity::getId).collect(Collectors.toSet());
            projectIdDeviceOrderTotalAmountMap.putAll(this.getProjectDeviceTotalOrderValues(projectIds));
        }
        List<ProjectListVO> projectListVOList = projectEntities.stream()
                .map(projectEntity -> {
                    this.calcAndSaveStatus(projectEntity);
                    ProjectListVO projectListVO = new ProjectListVO();
                    BeanUtil.copyProperties(projectEntity, projectListVO);
                    if (AuthenticationHolder.isAdmin()) {
                        projectListVO.setTotalAmount(projectIdDeviceOrderTotalAmountMap.get(projectEntity.getId()));
                    }
                    return projectListVO;
                }).collect(Collectors.toList());
        PageResult<ProjectListVO> pg = new PageResult<>();
        pg.setElements(projectListVOList);
        pg.setTotalSize(page.getTotalElements());
        return pg;
    }

    private Specification<ProjectEntity> getSpec(String keyword, String userId) {
        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = Lists.newArrayList();
            if (AuthenticationHolder.isAdmin()) {
                // 管理员
                if (!StringUtils.isBlank(userId)) {
                    predicates.add(criteriaBuilder.equal(root.get("userId"), userId));
                }
            } else {
                // 商家
                predicates.add(criteriaBuilder.equal(root.get("userId"), AuthenticationHolder.getUserId()));
            }
            if (!StringUtils.isBlank(keyword)) {
                predicates.add(criteriaBuilder.like(root.get("name"), "%" + keyword + "%"));
            }
            Predicate[] preArr = new Predicate[predicates.size()];
            return criteriaBuilder.and(predicates.toArray(preArr));
        };
    }

    @Override
    public PageResult<ProjectListVO> fiSearchBorrowerProject(Pageable pageable, String borrowerId) {
        StringBuilder pageSqlBuilder = new StringBuilder("SELECT p.* FROM project p JOIN loan l ON l.id=p.loan_id WHERE" +
                " l.borrower_id=:borrowerId AND l.lender_id=:lenderId AND l.status !='CREATING' LIMIT :start,:end");
        StringBuilder totalNumBuilder = new StringBuilder("SELECT count(p.id) FROM project p JOIN loan l ON l.id=p.loan_id" +
                " WHERE l.borrower_id=:borrowerId AND l.lender_id=:lenderId AND l.status !='CREATING'");
        Query pageQuery = entityManager.createNativeQuery(pageSqlBuilder.toString(), ProjectEntity.class);
        pageQuery.setParameter("borrowerId", borrowerId);
        pageQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        pageQuery.setParameter("start", pageable.getPageSize() * pageable.getPageNumber());
        pageQuery.setParameter("end", pageable.getPageSize());
        List<ProjectEntity> projectEntities = pageQuery.getResultList();
        Set<String> projectIds = projectEntities.stream().map(ProjectEntity::getId).collect(Collectors.toSet());
        Map<String, String> projectIdDeviceOrderTotalAmountMap = this.getProjectDeviceTotalOrderValues(projectIds);
        List<ProjectListVO> projectListVOList = projectEntities.stream()
                .map(projectEntity -> {
                    this.calcAndSaveStatus(projectEntity);
                    ProjectListVO projectListVO = new ProjectListVO();
                    BeanUtil.copyProperties(projectEntity, projectListVO);
                    projectListVO.setTotalAmount(projectIdDeviceOrderTotalAmountMap.get(projectEntity.getId()));
                    return projectListVO;
                }).collect(Collectors.toList());
        PageResult<ProjectListVO> pageResult = new PageResult<>();
        pageResult.setElements(projectListVOList);
        Query totalNumQuery = entityManager.createNativeQuery(totalNumBuilder.toString());
        totalNumQuery.setParameter("borrowerId", borrowerId);
        totalNumQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        BigInteger count = (BigInteger) QueryUtil.getSingleResult(totalNumQuery);
        if (null == count) {
            count = BigInteger.ZERO;
        }
        pageResult.setTotalSize(count.longValue());
        return pageResult;
    }

    private void calcAndSaveStatus(ProjectEntity projectEntity) {
        long now = System.currentTimeMillis();
        long operaTime = Long.parseLong(projectEntity.getOperationStartTime());
        long endTime = Long.parseLong(projectEntity.getProjectDueTime());

        if (now >= operaTime && now <= endTime) {
            projectEntity.setStatus(ProjectStatus.BUSINESS);
        } else if (now > endTime) {
            projectEntity.setStatus(ProjectStatus.END);
        }
        projectEntityRepo.save(projectEntity);
    }

    @Override
    public List<BorrowerProjectExportVO> fiExportBorrowerProject(String borrowerId) {
        StringBuilder sqlBuilder = new StringBuilder("SELECT p.* FROM project p WHERE EXISTS (" +
                " SELECT l.id  FROM loan l WHERE l.borrower_id=:borrowerId AND l.lender_id=:lenderId AND l.id=p.loan_id AND l.STATUS !='CREATING')");
        Query nativeQuery = entityManager.createNativeQuery(sqlBuilder.toString(), ProjectEntity.class);
        nativeQuery.setParameter("borrowerId", borrowerId);
        nativeQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        List<ProjectEntity> projectEntities = nativeQuery.getResultList();
        return this.exportBorrowerProject(projectEntities);
    }

    private List<BorrowerProjectExportVO> exportBorrowerProject(List<ProjectEntity> projectEntities) {
        if (CollectionUtils.isEmpty(projectEntities)) {
            return new ArrayList<>();
        }
        Set<String> projectIds = projectEntities.stream().map(ProjectEntity::getId).collect(Collectors.toSet());
        Map<String, String> projectIdDeviceOrderTotalAmountMap = this.getProjectDeviceTotalOrderValues(projectIds);
        return projectEntities.stream()
                .map(projectEntity -> {
                    this.calcAndSaveStatus(projectEntity);
                    BorrowerProjectExportVO borrowerProjectExportVO = new BorrowerProjectExportVO();
                    BeanUtils.copyProperties(projectEntity, borrowerProjectExportVO, "winningBidTime", "projectDueTime", "type", "status");
                    borrowerProjectExportVO.setTotalAmount(projectIdDeviceOrderTotalAmountMap.get(projectEntity.getId()));
                    borrowerProjectExportVO.setType(projectEntity.getType().getMessage());
                    borrowerProjectExportVO.setStatus(projectEntity.getStatus().getMessage());
                    borrowerProjectExportVO.setWinningBidTime(new Date(Long.parseLong(projectEntity.getWinningBidTime())));
                    borrowerProjectExportVO.setProjectDueTime(new Date(Long.parseLong(projectEntity.getProjectDueTime())));
                    return borrowerProjectExportVO;
                }).collect(Collectors.toList());
    }

    @Override
    public List<BorrowerProjectExportVO> adminExportBorrowerProject(String borrowerId) {
        Specification<ProjectEntity> spec = this.getSpec(null, borrowerId);
        List<ProjectEntity> projectEntities = projectEntityRepo.findAll(spec);
        return this.exportBorrowerProject(projectEntities);
    }

    @Override
    public ProjectDetailVO getProjectDetail(String projectId) {
        ProjectDetailVO projectDetailVO = new ProjectDetailVO();
        Optional<ProjectEntity> project = projectEntityRepo.findById(projectId);
        if (!project.isPresent()) {
            throw new RuntimeException("找不到id:" + projectId + "对应的项目");
        }
        ProjectEntity projectEntity = project.get();
        BeanUtils.copyProperties(projectEntity, projectDetailVO);
        List<AttachVO> attachment = attachmentService.getAttachment(projectId, AttachType.BID_DOCUMENT);
        if (!CollectionUtils.isEmpty(attachment)) {
            AttachVO attachVO = attachment.get(0);
            projectDetailVO.setBidId(attachVO.getId());
            projectDetailVO.setBidFileName(attachVO.getName());
            projectDetailVO.setBidUrl(attachVO.getUrl());
        }

        String totalOrderValue = getProjectDeviceTotalOrderValue(projectId);
        projectDetailVO.setTotalOrderValue(totalOrderValue);
        Long projectInteractedDevices = deviceService.countProjectInteractedDevices(projectId);
        projectDetailVO.setTotalDeviceCount(projectInteractedDevices);
        Long todayInteractedDevices = deviceService.countProjectTodayInteractedDevices(projectId);
        projectDetailVO.setNormalDeviceCount(todayInteractedDevices);

        String loanId = projectEntity.getLoanId();
        if (StringUtils.isBlank(loanId)) {
            projectDetailVO.setIfDeleted(true);
            projectDetailVO.setIfEdit(true);
        } else {
            LoanEntity loanEntity = loanEntityRepo.findById(loanId).orElseThrow(() -> new BusinessException(Code.NODATA));
            if (loanEntity.getStatus().equals(LoanStatus.CREATING) || loanEntity.getStatus().equals(LoanStatus.FAIL)) {
                projectDetailVO.setIfDeleted(false);
                projectDetailVO.setIfEdit(true);
            } else {
                projectDetailVO.setIfDeleted(false);
                projectDetailVO.setIfEdit(false);
            }
        }

        return projectDetailVO;
    }

    @Override
    public Long countSchoolByUserId(String userId) {
        if (AuthenticationHolder.isAdmin()) {
            return projectEntityRepo.countDistinctBelongSchoolByUserId(userId);
        }
        return projectEntityRepo.countDistinctBelongSchoolByUserId(AuthenticationHolder.getUserId());
    }

    @Override
    @Transactional
    public void deleteProject(String projectId) {
        Optional<ProjectEntity> project = projectEntityRepo.findById(projectId);
        if (!project.isPresent()) {
            throw new RuntimeException("找不到id:" + projectId + "对应的项目");
        }
        ProjectEntity projectEntity = project.get();
        if (StringUtils.isNotBlank(projectEntity.getLoanId())) {
            throw new RuntimeException("该项目有已经关联贷款，无法删除");
        }
        deviceService.unbindProject(projectId);
        projectEntity.setUserId(null);
        projectEntityRepo.save(projectEntity);
    }

    @Override
    public List<ProjectStatusQueryVO> getProjectListByStatus(Integer status) {
        List<ProjectEntity> list = projectEntityRepo.findAllByUserIdAndStatus(AuthenticationHolder.getUserId(), ProjectStatus.getStatusByCode(status).name());
        List<ProjectStatusQueryVO> result = list.stream().map(x -> {
            return ProjectStatusQueryVO.builder().id(x.getId()).name(x.getName()).build();
        }).collect(Collectors.toList());
        return result;
    }

    @Override
    public ProjectDetailVO borrowerProjectDetail(String projectId) {
        StringBuilder sqlBuilder = new StringBuilder("select p.* from project p where p.id=:projectId and exists(" +
                " select l.id from loan l where l.borrower_id=p.user_id and l.lender_id=:lenderId and l.status!='CREATING')");
        Query nativeQuery = entityManager.createNativeQuery(sqlBuilder.toString(), ProjectEntity.class);
        nativeQuery.setParameter("projectId", projectId);
        nativeQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        ProjectEntity projectEntity = (ProjectEntity) QueryUtil.getSingleResult(nativeQuery);
        if (null == projectEntity) {
            throw new BusinessException(Code.NODATA, "项目不存在");
        }
        ProjectDetailVO projectDetailVO = new ProjectDetailVO();
        BeanUtils.copyProperties(projectEntity, projectDetailVO);
        List<AttachVO> attachment = attachmentService.getAttachment(projectId, AttachType.BID_DOCUMENT);
        if (!CollectionUtils.isEmpty(attachment)) {
            AttachVO attachVO = attachment.get(0);
            projectDetailVO.setBidFileName(attachVO.getName());
            projectDetailVO.setBidUrl(attachVO.getUrl());
        }
        String totalOrderValue = getProjectDeviceTotalOrderValue(projectId);
        projectDetailVO.setTotalOrderValue(totalOrderValue);
        Long projectInteractedDevices = deviceService.countBorrowerInteractedDevices(projectId);
        Long projectTodayInteractedDevices = deviceService.countBorrowerTodayInteractedDevices(projectId);
        projectDetailVO.setTotalDeviceCount(projectInteractedDevices);
        projectDetailVO.setNormalDeviceCount(projectTodayInteractedDevices);
        return projectDetailVO;
    }

    @Override
    public void relateLoan(String loanId, List<String> projectIds) {
        List<ProjectEntity> list = projectEntityRepo.findAllById(projectIds);
        list.forEach(x -> {
            if (!StringUtils.isBlank(x.getLoanId())) {
                throw new RuntimeException("该项目已有绑定的贷款项目");
            }
        });
        StringBuilder sb = new StringBuilder("update project set loan_id = :loanId where id in(:projectIds)");
        Query query = entityManager.createNativeQuery(sb.toString());
        query.setParameter("loanId", loanId);
        query.getSingleResult();
    }

    @Override
    public PageResult<BorrowerHistoryProjectListVO> getBorrowerHistoryProjectList(Pageable pageable, String borrowerId) {
        StringBuilder sb = new StringBuilder("SELECT p.id,p.name,p.type,p.belong_school,p.device_count,p.winning_bid_time" +
                ",p.project_due_time from loan l JOIN project p on l.id = p.loan_id WHERE l.borrower_id = :borrowerId" +
                " and l.lender_id = :lenderId");
        Query countQuery = entityManager.createNativeQuery(sb.toString());
        countQuery.setParameter("borrowerId", borrowerId);
        countQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        Long count = (Long) Optional.ofNullable(countQuery.getSingleResult()).orElse(0L);
        sb.append(" limit :start,:end");
        Query query = entityManager.createNativeQuery(sb.toString());
        query.setParameter("borrowerId", borrowerId);
        query.setParameter("lenderId", AuthenticationHolder.getUserId());
        query.setParameter("start", (pageable.getPageNumber() - 1) * pageable.getPageSize());
        query.setParameter("end", pageable.getPageSize());
        List<Object[]> list = query.getResultList();
        List<BorrowerHistoryProjectListVO> collect = list.stream().map(x -> {
            return BorrowerHistoryProjectListVO.builder().id((String) x[0]).name((String) x[1]).type((String) x[2])
                    .belongSchool((String) x[3]).deviceCount((Integer) x[4]).winningBidTime((String) x[5])
                    .projectDueTime((String) x[6]).build();
        }).collect(Collectors.toList());
        PageResult pg = new PageResult(count, collect);
        return pg;
    }

    @Override
    public List<BorrowerHistoryProjectExportVO> exportBorrowerHistoryProjectList(String borrowerId) {
        StringBuilder sb = new StringBuilder("SELECT p.name,p.type,p.belong_school,p.device_count,p.winning_bid_time" +
                ",p.project_due_time from loan l JOIN project p on l.id = p.loan_id WHERE l.borrower_id = :borrowerId" +
                " and l.lender_id = :lenderId");
        Query query = entityManager.createNativeQuery(sb.toString());
        query.setParameter("borrowerId", borrowerId);
        query.setParameter("lenderId", AuthenticationHolder.getUserId());
        List<Object[]> list = query.getResultList();
        List<BorrowerHistoryProjectExportVO> result = list.stream().map(x -> {
            return BorrowerHistoryProjectExportVO.builder().name((String) x[0]).type((String) x[1])
                    .belongSchool((String) x[2]).deviceCount((Integer) x[3]).winningBidTime((String) x[4])
                    .projectDueTime((String) x[5]).build();
        }).collect(Collectors.toList());
        return result;
    }

    private String getProjectDeviceTotalOrderValue(String projectId) {
        StringBuilder sb = new StringBuilder();
        sb.append("select sum(o.price) from device_order o where o.project_id = :projectId");
        Query que = entityManager.createNativeQuery(sb.toString());
        que.setParameter("projectId", projectId);
        BigDecimal totalValue = (BigDecimal) QueryUtil.getSingleResult(que);
        return AmountUtil.amountRoundStr(totalValue, 0);
    }

    private Map<String, String> getProjectDeviceTotalOrderValues(Collection<String> projectIds) {
        if (CollectionUtil.isEmpty(projectIds)) {
            return new HashMap<>();
        }
        StringBuilder sb = new StringBuilder();
        sb.append("select o.project_id,sum(o.price) from device_order o where o.project_id in(:projectIds) group by o.project_id");
        Query query = entityManager.createNativeQuery(sb.toString());
        query.setParameter("projectIds", projectIds);
        List<Object[]> resultList = query.getResultList();
        if (CollectionUtil.isEmpty(resultList)) {
            return new HashMap<>();
        }
        Function<Object[], String> projectIdMapper = objs -> (String) objs[0];
        Function<Object[], String> sumPriceMapper = objs -> {
            BigDecimal amount = (BigDecimal) objs[1];
            return AmountUtil.amountRoundStr(amount, 0);
        };
        return resultList.stream().collect(Collectors.toMap(projectIdMapper, sumPriceMapper));
    }
}
